use std::{
    future::{ready, Ready},
    mem::MaybeUninit,
};

use actix_web::{dev::Payload, FromRequest, HttpRequest};
use dashmap::DashMap;

use crate::{
    error::{new_err, new_err_info, AppErr, ErrWrap, EC_USER_LOGIN_TIMEOUT, EC_USER_NOT_LOGIN},
    utils::current_timestamp,
    web::cfg::HTTP_USER_SERSSION_HEAD,
};

static mut USER_MAP: MaybeUninit<DashMap<i64, UserSession>> = MaybeUninit::uninit();

pub const LOGIN_MAX_DURATION: u64 = 60 * 60 * 4;

#[derive(Debug, Clone)]
pub struct UserSession {
    pub id: i64,
    login_timestamp: u64,
}

pub fn init() {
    unsafe {
        USER_MAP.write(DashMap::new());
    }
}

fn get_map() -> &'static DashMap<i64, UserSession> {
    unsafe { USER_MAP.assume_init_ref() }
}

pub fn save_session(user_id: i64) {
    let map = get_map();
    let session = UserSession {
        id: user_id,
        login_timestamp: current_timestamp(),
    };
    map.insert(user_id, session);
}


pub fn get_user_session(id: i64) -> Result<UserSession, AppErr> {
    let map = get_map();
    let session = map
        .get(&id)
        .ok_or(new_err_info(EC_USER_NOT_LOGIN, "没有登陆"))?
        .value()
        .clone();

    let current = current_timestamp();
    if current > (session.login_timestamp + LOGIN_MAX_DURATION) {
        return Err(new_err_info(EC_USER_LOGIN_TIMEOUT, "登陆超时 请重新登陆"));
    }
    save_session(id);

    Ok(session)
}

fn get_session(req: &HttpRequest) -> Result<UserSession, AppErr> {
    let id: i64 = req
        .headers()
        .get(HTTP_USER_SERSSION_HEAD)
        .ok_or(new_err("no user token"))?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;

    Ok( get_user_session(id)? )
}

fn get_empty(req: &HttpRequest) -> Result<EmptyUserSession, AppErr> {

    let id: i64 = req
        .headers()
        .get(HTTP_USER_SERSSION_HEAD)
        .ok_or(new_err("no user token"))?
        .to_str()
        .wrap()?
        .parse()
        .wrap()?;

    let map = get_map();
    map.get(&id)
        .ok_or(new_err_info(EC_USER_NOT_LOGIN, "没有登陆"))?;

    Ok(EmptyUserSession)
}

pub struct EmptyUserSession;

impl FromRequest for EmptyUserSession {
    type Error = AppErr;
    type Future = Ready<Result<Self, AppErr>>;
    fn from_request(req: &HttpRequest, _p: &mut Payload) -> Self::Future {
        ready(get_empty(req))
    }
}

impl FromRequest for UserSession {
    type Error = AppErr;
    type Future = Ready<Result<Self, AppErr>>;

    fn from_request(req: &HttpRequest, _p: &mut Payload) -> Self::Future {
        ready(get_session(req))
    }
}
